#!/usr/bin/python -S
"""
sync.py -- synchronization primitives

Other ones:
  select()?
  semaphore()?
"""

__author__ = 'Andy Chu'


import collections
import sys

import timer


# torn.sync module -- synchronization primitives
class Waiter(object):
  def __init__(self):
    self.counter = 0
    self.callback = None

  def inc(self):
    self.counter += 1
    print '\t\tinc (%d)' % self.counter

  def dec(self):
    self.counter -= 1
    print '\t\tdec (%d)' % self.counter

    if self.counter < 0:
      raise AssertionError('dec() called too many times')

    if self.counter == 0:
      assert self.callback, 'wait not called'
      self.callback()

  # This could have a single timeout?
  # waiting for multiple thingies
  # maybe inc() takes them
  # and after the timeout, it cancels everything that's still pending?

  def wait(self, callback):
    """Wait until the counter is 0."""
    if self.counter == 0:
      callback()
    else:
      self.callback = callback


class Pool(object):
  """
  Can be accessed only from the event loop thread.  See node.js demos.
  """
  def __init__(self, take_deadline=None):
    self.items = collections.deque()
    # list of callbacks to call
    self.waiting = []
    self.take_deadline = take_deadline

  def take(self, callback, timeout_cb=None):
    timeout_cb = timeout_cb or (lambda: None)
    if self.items:
      # should it be a deque()?
      item = self.items.popleft()
      callback(item)
    else:
      def remove():
        print 'before removing, waiting =', self.waiting
        print 'pair', pair, id(pair)
        print 'timeout_cb =', timeout_cb
        self.waiting.remove(pair)
        timeout_cb()
      #end remove

      if self.take_deadline:
        print 'setting timeout', self.take_deadline
        timeout = timer.set_timeout(self.take_deadline, remove)
        pair = (callback, timeout)
      else:
        pair = (callback, None)

      #  TODO: If we call the callback, we should cancel the timer...
      # EITHER the timeout_cb or the regular cb should be executed, not both!

      # push a callback for the next item; put() will remove and call these
      print 'before adding, waiting =', self.waiting
      print 'adding to waiting', pair, id(pair)
      self.waiting.append(pair)

  def put(self, item):
    if self.waiting:
      # if we have callbacks, pop the first one and call it with this return
      # item
      callback, timeout = self.waiting.pop()
      if timeout:
        timer.clear_timeout(timeout)
      callback(item)
    else:
      self.items.append(item)
